package be.ipl.diapero.domaine;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;

@Entity
@Table(schema = "DIAVOLO", name = "PARTIES")
@SuppressWarnings("serial")
public class Partie implements Serializable {

	public enum Etat {

		EN_ATTENTE {
			@Override
			public boolean ajouterJoueur(Joueur joueur, Partie partie) {
				if (partie.joueurs.containsKey(joueur))
					return false;
				partie.joueurs.put(joueur, NBR_GEMMES_INITIAL);
				return true;
			}

			@Override
			public boolean commencerPartie(Partie partie) {
				if (partie.getJoueurs().size() < 2)
					return false;
				partie.etat = EN_COURS;
				return true;
			}

			@Override
			public boolean annulerPartie(Partie partie) {
				partie.etat = ANNULEE;
				return true;
			}
		},

		ANNULEE {

			@Override
			public boolean initialiserPartie(Partie partie) {
				partie.joueurs = new HashMap<Joueur, Integer>();
				return true;
			}
		},

		EN_COURS {
			@Override
			public Tour commencerTourSuivant(Partie partie) {
				if (partie.zeroOuUnJoueurRestant()) {
					return null;
				}
				Tour tour = new Tour(partie.tours.size() + 1);

				if (!partie.getTours().contains(tour))
					partie.getTours().add(tour);
				return tour;
			}

			@Override
			public boolean diminuerGemmes(Joueur joueur, Partie partie) {
				if (partie.getGemmes(joueur) <= 0)
					return false;
				partie.joueurs.put(joueur, (partie.joueurs.get(joueur) - 1));
				return true;
			}

			@Override
			public boolean augmenterGemmes(Joueur joueur, Partie partie) {
				if (partie.getGemmes(joueur) == Partie.NBR_GEMMES_INITIAL)
					return false;
				partie.joueurs.put(joueur, (partie.joueurs.get(joueur) + 1));
				return true;
			}

			@Override
			public boolean terminerPartie(Partie partie) {
				partie.etat = TERMINE;
				return true;
			}
		},

		TERMINE {

			@Override
			public Joueur getVainqueur(Partie partie) {
				for (Joueur joueur : partie.getJoueurs()) {
					if (partie.getGemmes(joueur) > 0) {
						return joueur;
					}
				}
				return null;
			}

		};

		public boolean ajouterJoueur(Joueur joueur, Partie partie) {
			return false;
		}

		public boolean commencerPartie(Partie partie) {
			return false;
		}

		public Tour commencerTourSuivant(Partie partie) {
			return null;
		}

		public boolean initialiserPartie(Partie partie) {
			return false;
		}

		public boolean annulerPartie(Partie partie) {
			return false;
		}

		public boolean terminerPartie(Partie partie) {
			return false;
		}

		public boolean diminuerGemmes(Joueur joueur, Partie partie) {
			return false;
		}

		public boolean augmenterGemmes(Joueur joueur, Partie partie) {
			return false;
		}

		public Joueur getVainqueur(Partie partie) {
			return null;
		}
	}

	public static final int MAX_JOUEURS = 6;
	public static final int NBR_GEMMES_INITIAL = 5;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@NotNull
	@Column(unique = true)
	private String nom;

	@NotNull
	@Enumerated(EnumType.STRING)
	private Etat etat;

	@NotNull
	@Temporal(TemporalType.TIMESTAMP)
	private Calendar date;
	
	@OneToMany(cascade = CascadeType.ALL)
	@JoinColumn(name = "PARTIE_ID")
	private List<Tour> tours = new ArrayList<Tour>();

	@ElementCollection
	@MapKeyJoinColumn(name = "JOUEUR_ID")
	@Column(name = "NB_GEMMES")
	@CollectionTable(schema = "DIAVOLO", name = "JOUEURS_PARTIES")
	private Map<Joueur, Integer> joueurs = new HashMap<Joueur, Integer>();

	public boolean ajouterJoueur(Joueur joueur) {
		return etat.ajouterJoueur(joueur, this);
	}

	public boolean commencerPartie() {
		return etat.commencerPartie(this);
	}

	public boolean annulerPartie() {
		return etat.annulerPartie(this);
	}

	public boolean terminerPartie() {
		return etat.terminerPartie(this);
	}

	public Tour commencerTourSuivant() {
		return etat.commencerTourSuivant(this);
	}

	public boolean diminuerGemmes(Joueur joueur) {
		return etat.diminuerGemmes(joueur, this);
	}

	public boolean augmenterGemmes(Joueur joueur) {
		return etat.augmenterGemmes(joueur, this);
	}

	public Joueur getVainqueur() {
		return etat.getVainqueur(this);
	}

	protected boolean zeroOuUnJoueurRestant() {
		int nbJoueurAuMoinsUneBiere = 0;
		for (Joueur joueur : joueurs.keySet()) {
			if (joueurs.get(joueur) > 0) {
				nbJoueurAuMoinsUneBiere++;
			}
		}
		return nbJoueurAuMoinsUneBiere <= 1;
	}

	protected Partie() {
		super();
	}

	public Partie(String nom, Calendar date) {
		super();
		this.nom = nom;
		this.date = date;
		this.etat = Etat.EN_ATTENTE;
	}

	public int getId() {
		return id;
	}

	public String getNom() {
		return nom;
	}

	public Calendar getDate() {
		return date;
	}

	public Etat getEtat() {
		return etat;
	}

	public List<Joueur> getJoueurs() {
		return new ArrayList<Joueur>(joueurs.keySet());
	}

	public int getGemmes(Joueur joueur) {
		return joueurs.get(joueur);
	}

	public List<Tour> getTours() {
		return tours;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Partie other = (Partie) obj;
		if (id != other.id)
			return false;
		return true;
	}

}
